import { Injectable, BadRequestException, ConflictException } from '@nestjs/common';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Track, ResourceFile } from '../entities';
import { CrudRequest } from '@nestjsx/crud';
import { DeepPartial, Repository, QueryFailedError } from 'typeorm';
import { FileUtils, AudioUtils } from '@/utils';
import { FileStoragePath } from '@/enum';
import * as path from 'path';
import * as _ from 'lodash';
import { plainToClass } from 'class-transformer';

@Injectable()
export class TracksService extends TypeOrmCrudService<Track> {
  constructor(
    @InjectRepository(Track) repo,
    @InjectRepository(ResourceFile)
    private readonly fileRepo: Repository<ResourceFile>,
  ) {
    super(repo);
  }

  async createOne(req: CrudRequest, dto: DeepPartial<Track>): Promise<Track> {
    if (dto.file) {
      const metadata = await this.saveAudioFile(dto.file);
      dto.duration = metadata.format.duration;
    }
    return super.createOne(req, dto);
  }

  async updateOne(req: CrudRequest, dto: DeepPartial<Track>): Promise<Track> {
    if (dto.file && !dto.file.id) {
      const metadata = await this.saveAudioFile(dto.file);
      dto.duration = metadata.format.duration;
    }
    return super.updateOne(req, dto);
  }

  async replaceOne(req: CrudRequest, dto: DeepPartial<Track>): Promise<Track> {
    if (dto.file && !dto.file.id) {
      const metadata = await this.saveAudioFile(dto.file);
      dto.duration = metadata.format.duration;
    }
    return super.replaceOne(req, dto);
  }

  /**
   * Soft delete one entity.
   *
   * Override from the `Crud` `deleteOne` function to soft delete.
   * @param req `CrudRequest` from `Crud`
   */
  async deleteOne(req: CrudRequest): Promise<void | Track> {
    const { returnDeleted } = req.options.routes.deleteOneBase;
    const found = await this.getOneOrFail(req, returnDeleted);
    const toReturn = returnDeleted
      ? plainToClass(this.entityType, { ...found })
      : undefined;
    await this.repo.softRemove(found);
    return toReturn;
  }

  private async saveAudioFile(file: DeepPartial<ResourceFile>) {
    const audio = await this.saveFile(file);
    return await AudioUtils.getMetadata(
      path.resolve(process.cwd(), audio.path),
    );
  }

  private async saveFile(file: DeepPartial<ResourceFile>) {
    file.sha256 = await FileUtils.getHashOfFile(file.path);
    let path = '';
    switch (FileUtils.getTypeFromMimetype(file.mimetype)) {
      case 'audio':
        path = FileStoragePath.UPLOAD_AUDIOS;
        break;
      default:
        throw new BadRequestException(
          `Unexpected file type "${file.mimetype}" of file ${file.originalname}`,
        );
    }
    path += file.filename;
    await FileUtils.moveFile(file.path, path);
    file.path = path;
    return await this.fileRepo.save(file).catch((err: QueryFailedError) => {
      const reg = /^duplicate key value violates unique constraint "UQ_[0-9a-f]+"$/i;
      if (reg.test(err.message)) {
        throw new ConflictException(
          '文件已经被上传过了',
          `duplicate_file: ${file.originalname}`,
        );
      }
      throw err;
    });
  }
}
